/* Copyright (c) 2006-2010 by OpenLayers Contributors (see authors.txt for 
 * full list of contributors). Published under the Clear BSD license.  
 * See http://svn.openlayers.org/trunk/openlayers/license.txt for the
 * full text of the license. */

/**
 * @requires OpenLayers/Format/XML.js
 * @requires OpenLayers/Feature/Vector.js
 * @requires OpenLayers/Geometry/Point.js
 * @requires OpenLayers/Geometry/MultiPoint.js
 * @requires OpenLayers/Geometry/LineString.js
 * @requires OpenLayers/Geometry/MultiLineString.js
 * @requires OpenLayers/Geometry/Polygon.js
 * @requires OpenLayers/Geometry/MultiPolygon.js
 * @requires OpenLayers/Console.js
 */

/**
 * Class: OpenLayers.Format.GML
 * Read/Wite GML. Create a new instance with the <OpenLayers.Format.GML>
 *     constructor.  Supports the GML simple features profile.
 * 
 * Inherits from:
 *  - <OpenLayers.Format>
 */
OpenLayers.Format.RestJSON = OpenLayers.Class(OpenLayers.Format,{
	extractAttributes: true,
    /**
     * APIProperty: xy
     * {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x)
     * Changing is not recommended, a new Format should be instantiated.
     */ 
    xy: true,
    
    /**
     * Constructor: OpenLayers.Format.GML
     * Create a new parser for GML.
     *
     * Parameters:
     * options - {Object} An optional object whose properties will be set on
     *     this instance.
     */
    initialize: function(options) {
    	OpenLayers.Format.prototype.initialize.apply(this, [options]);
    },

    /**
     * APIMethod: read
     * Read data from a string, and return a list of features. 
     * 
     * Parameters:
     * data - {String} or {DOMElement} data to read/parse.
     *
     * Returns:
     * {Array(<OpenLayers.Feature.Vector>)} An array of features.
     */
    read: function(data) {
    	var features = [];
    	if(data) {
    		var featuresdata = data.features;
	    	if(featuresdata) {
	    		var url = data.url;
		    	if(url && url.indexOf("geoserver") > -1) {
		    		for(var i=0; i<featuresdata.length; i++) {
			            var feature = this.parseFeatureForGeoServer(featuresdata[i]);
			            if(feature) {
			                features.push(feature);
			            }
			        }
		    	} else {
			    	var geometrytype = data.geometryType;
			        for(var i=0; i<featuresdata.length; i++) {
			            var feature = this.parseFeature(featuresdata[i], geometrytype);
			            if(feature) {
			                features.push(feature);
			            }
			        }   	
		    	}
	    	}
    	}
        return features;
    },
    
    /**
     * Method: parseFeature
     * This function is the core of the GML parsing code in OpenLayers.
     *    It creates the geometries that are then attached to the returned
     *    feature, and calls parseAttributes() to get attribute data out.
     *    
     * Parameters:
     * node - {DOMElement} A GML feature node. 
     */
    parseFeature: function(featuredata, geometrytype) {
        var parsers = {"esriGeometryPoint": "point", "esriGeometryPolygon" : "rings","esriGeometryPolyline": "paths"};
        var geometrydata = featuredata.geometry;
        var geometry = null;
		
        var type = parsers[geometrytype];
        // only deal with first geometry of this type
        var parser = this.parseGeometry[type];
        if(parser) {
            geometry = parser.apply(this, [geometrydata, type]);
            if (this.internalProjection && this.externalProjection) {
                geometry.transform(this.externalProjection, 
                                   this.internalProjection); 
            }
        } else {
            OpenLayers.Console.error(OpenLayers.i18n(
                        "unsupportedGeometryType", {'geomType':type}));
        }
        
        var bounds = null;
        if(geometry){
        	geometry.type = geometrytype;
        	bounds = geometry.getBounds();
        }
        
        // construct feature (optionally with attributes)
        var attributes;
        if(this.extractAttributes) {
        	attributes = featuredata.attributes;
        }
        var feature = new OpenLayers.Feature.Vector(geometry, attributes);
        feature.bounds = bounds;
        
        feature.fid = featuredata.attributes ? featuredata.attributes["FID"] : new Date().getTime();
        return feature;
    },
    
    parseFeatureForGeoServer: function(featuredata) {
        var geometrydata = featuredata.geometry;
        var geometry = null;
		
        var type = geometrydata.type;
        // only deal with first geometry of this type
        var parser = this.parseGeometryForGeoServer[type];
        if(parser) {
            geometry = parser.apply(this, [geometrydata.coordinates]);
            if (this.internalProjection && this.externalProjection) {
                geometry.transform(this.externalProjection, 
                                   this.internalProjection); 
            }
        } else {
            OpenLayers.Console.error(OpenLayers.i18n(
                        "unsupportedGeometryType", {'geomType':type}));
        }
        
        var bounds = null;
        if(geometry){
        	bounds = geometry.getBounds();
        }
        
        // construct feature (optionally with attributes)
        var attributes;
        if(this.extractAttributes) {
        	attributes = featuredata.properties;
        }
        var feature = new OpenLayers.Feature.Vector(geometry, attributes);
        feature.bounds = bounds;
        
        feature.fid = featuredata.attributes ? featuredata.id : new Date().getTime();
        return feature;
    },
    
    /**
     * Property: parseGeometry
     * Properties of this object are the functions that parse geometries based
     *     on their type.
     */
    parseGeometry: {
        point: function(datas, type) {
        	var x = datas["x"],y = datas["y"];
        	var point = new OpenLayers.Geometry.Point(x, y);
            return point;
        },
        
        multipoint: function(datas, type) {
            
            return null;
        },
        
        linestring: function(datas, type, ring) {
        	var points = [];
        	for(var i in datas){
        		var data = datas[i];
        		var x = data[0],y = data[1];
        		if(!this.xy){
        			x = y;
        			y = data[0];
        		}
        		
        		points.push(new OpenLayers.Geometry.Point(x, y));
        	}
        	var line = null;
        	if(points.length != 0) {
                if(ring) {
                    line = new OpenLayers.Geometry.LinearRing(points);
                } else {
                    line = new OpenLayers.Geometry.LineString(points);
                }
            }
            return line;
        },
        
        paths: function(datas, type) {
        	datas = datas[type];
        	var components = [];
        	if(datas.length > 0) {
        		var line;
                for(var i=0; i<datas.length; ++i) {
                	line = this.parseGeometry.linestring.apply(this,
                                                        [datas[i], null, false]);
                    if(line) {
                        components.push(line);
                    }
                }
        	}
        	return new OpenLayers.Geometry.MultiLineString(components);
        },
        
        rings: function(datas, type) {
        	datas = datas[type];
            var components = [];
            if(datas.length > 0) {
                // this assumes exterior ring first, inner rings after
                var ring;
                for(var i=0; i<datas.length; ++i) {
                    ring = this.parseGeometry.linestring.apply(this,
                                                        [datas[i], null, true]);
                    if(ring) {
                    	var polygon = new OpenLayers.Geometry.Polygon([ring]);
                        components.push(polygon);
                    }
                }
            }
            return new OpenLayers.Geometry.MultiPolygon(components);
        },
        
        multipolygon: function(datas, type) {
            
            return null;
        },
        
        envelope: function(datas, type) {
            
            return null; 
        }
    },
    
    parseGeometryForGeoServer: {
        Point: function(datas) {
        	if (datas.length == 2){
	        	var x = datas[0],y = datas[1];
	        	var point = new OpenLayers.Geometry.Point(x, y);
	            return point;
        	}
        },
        
        MultiPoint: function(datas) {
            var points = [];
        	for(var i in datas){
        		var data = datas[i];
        		var x = data[0],y = data[1];
        		if(!this.xy){
        			x = y;
        			y = data[0];
        		}
        		
        		points.push(new OpenLayers.Geometry.Point(x, y));
        	}
            return new OpenLayers.Geometry.MultiPoint(points);
        },
        
        LineString: function(datas, ring) {
        	var points = [];
        	for(var i in datas){
        		var data = datas[i];
        		var x = data[0],y = data[1];
        		if(!this.xy){
        			x = y;
        			y = data[0];
        		}
        		
        		points.push(new OpenLayers.Geometry.Point(x, y));
        	}
        	var line = null;
        	if(points.length != 0) {
                if(ring) {
                    line = new OpenLayers.Geometry.LinearRing(points);
                } else {
                    line = new OpenLayers.Geometry.LineString(points);
                }
            }
            return line;
        },
        
        MultiLineString : function(datas) {
        	var components = [];
        	if(datas.length > 0) {
        		var line;
                for(var i=0; i<datas.length; ++i) {
                	line = this.parseGeometryForGeoServer.LineString.apply(this,
                                                        [datas[i], false]);
                    if(line) {
                        components.push(line);
                    }
                }
        	}
        	return new OpenLayers.Geometry.MultiLineString(components);
        },
        
        Polygon : function(datas) {
            var components = [];
            if(datas.length > 0) {
                // this assumes exterior ring first, inner rings after
                var lineString;
                for(var i=0; i<datas.length; ++i) {
                    lineString = this.parseGeometryForGeoServer.LineString.apply(this,
                                                        [datas[i], true]);
                    if(lineString) {
                        components.push(lineString);
                    }
                }
            }
            return new OpenLayers.Geometry.Polygon(components);
        },
        
        MultiPolygon : function(datas) {
            var components = [];
            if(datas.length > 0) {
                // this assumes exterior ring first, inner rings after
                var polygon;
                for(var i=0; i<datas.length; ++i) {
                    polygon = this.parseGeometryForGeoServer.Polygon.apply(this,
                                                        [datas[i]]);
                    if(polygon) {
                    	components.push(polygon);
                    }
                }
            }
            return new OpenLayers.Geometry.MultiPolygon(components);
        },
        
        Envelope: function(datas) {
            return null; 
        }
    },
    
    
    /**
     * APIMethod: write
     * Generate a GML document string given a list of features. 
     * 
     * Parameters:
     * features - {Array(<OpenLayers.Feature.Vector>)} List of features to
     *     serialize into a string.
     *
     * Returns:
     * {String} A string representing the GML document.
     */
    write: function(features) {
        if(!(features instanceof Array)) {
            features = [features];
        }
        var gml = this.createElementNS("http://www.opengis.net/wfs",
                                       "wfs:" + this.collectionName);
        for(var i=0; i<features.length; i++) {
            gml.appendChild(this.createFeatureXML(features[i]));
        }
        return OpenLayers.Format.XML.prototype.write.apply(this, [gml]);
    },

    /** 
     * Method: createFeatureXML
     * Accept an OpenLayers.Feature.Vector, and build a GML node for it.
     *
     * Parameters:
     * feature - {<OpenLayers.Feature.Vector>} The feature to be built as GML.
     *
     * Returns:
     * {DOMElement} A node reprensting the feature in GML.
     */
    createFeatureXML: function(feature) {
        var geometry = feature.geometry;
        var geometryNode = this.buildGeometryNode(geometry);
        var geomContainer = this.createElementNS(this.featureNS,
                                                 this.featurePrefix + ":" +
                                                 this.geometryName);
        geomContainer.appendChild(geometryNode);
        var featureNode = this.createElementNS(this.gmlns,
                                               "gml:" + this.featureName);
        var featureContainer = this.createElementNS(this.featureNS,
                                                    this.featurePrefix + ":" +
                                                    this.layerName);
        var fid = feature.fid || feature.id;
        featureContainer.setAttribute("fid", fid);
        featureContainer.appendChild(geomContainer);
        for(var attr in feature.attributes) {
            var attrText = this.createTextNode(feature.attributes[attr]); 
            var nodename = attr.substring(attr.lastIndexOf(":") + 1);
            var attrContainer = this.createElementNS(this.featureNS,
                                                     this.featurePrefix + ":" +
                                                     nodename);
            attrContainer.appendChild(attrText);
            featureContainer.appendChild(attrContainer);
        }    
        featureNode.appendChild(featureContainer);
        return featureNode;
    },
    
    /**
     * APIMethod: buildGeometryNode
     */
    buildGeometryNode: function(geometry) {
        if (this.externalProjection && this.internalProjection) {
            geometry = geometry.clone();
            geometry.transform(this.internalProjection, 
                               this.externalProjection);
        }    
        var className = geometry.CLASS_NAME;
        var type = className.substring(className.lastIndexOf(".") + 1);
        var builder = this.buildGeometry[type.toLowerCase()];
        return builder.apply(this, [geometry]);
    },

    /**
     * Property: buildGeometry
     * Object containing methods to do the actual geometry node building
     *     based on geometry type.
     */
    buildGeometry: {
        // TBD retrieve the srs from layer
        // srsName is non-standard, so not including it until it's right.
        // gml.setAttribute("srsName",
        //                  "http://www.opengis.net/gml/srs/epsg.xml#4326");

        /**
         * Method: buildGeometry.point
         * Given an OpenLayers point geometry, create a GML point.
         *
         * Parameters:
         * geometry - {<OpenLayers.Geometry.Point>} A point geometry.
         *
         * Returns:
         * {DOMElement} A GML point node.
         */
        point: function(geometry) {
            var gml = this.createElementNS(this.gmlns, "gml:Point");
            gml.appendChild(this.buildCoordinatesNode(geometry));
            return gml;
        },
        
        /**
         * Method: buildGeometry.multipoint
         * Given an OpenLayers multipoint geometry, create a GML multipoint.
         *
         * Parameters:
         * geometry - {<OpenLayers.Geometry.MultiPoint>} A multipoint geometry.
         *
         * Returns:
         * {DOMElement} A GML multipoint node.
         */
        multipoint: function(geometry) {
            var gml = this.createElementNS(this.gmlns, "gml:MultiPoint");
            var points = geometry.components;
            var pointMember, pointGeom;
            for(var i=0; i<points.length; i++) { 
                pointMember = this.createElementNS(this.gmlns,
                                                   "gml:pointMember");
                pointGeom = this.buildGeometry.point.apply(this,
                                                               [points[i]]);
                pointMember.appendChild(pointGeom);
                gml.appendChild(pointMember);
            }
            return gml;            
        },
        
        /**
         * Method: buildGeometry.linestring
         * Given an OpenLayers linestring geometry, create a GML linestring.
         *
         * Parameters:
         * geometry - {<OpenLayers.Geometry.LineString>} A linestring geometry.
         *
         * Returns:
         * {DOMElement} A GML linestring node.
         */
        linestring: function(geometry) {
            var gml = this.createElementNS(this.gmlns, "gml:LineString");
            gml.appendChild(this.buildCoordinatesNode(geometry));
            return gml;
        },
        
        /**
         * Method: buildGeometry.multilinestring
         * Given an OpenLayers multilinestring geometry, create a GML
         *     multilinestring.
         *
         * Parameters:
         * geometry - {<OpenLayers.Geometry.MultiLineString>} A multilinestring
         *     geometry.
         *
         * Returns:
         * {DOMElement} A GML multilinestring node.
         */
        multilinestring: function(geometry) {
            var gml = this.createElementNS(this.gmlns, "gml:MultiLineString");
            var lines = geometry.components;
            var lineMember, lineGeom;
            for(var i=0; i<lines.length; ++i) {
                lineMember = this.createElementNS(this.gmlns,
                                                  "gml:lineStringMember");
                lineGeom = this.buildGeometry.linestring.apply(this,
                                                                   [lines[i]]);
                lineMember.appendChild(lineGeom);
                gml.appendChild(lineMember);
            }
            return gml;
        },
        
        /**
         * Method: buildGeometry.linearring
         * Given an OpenLayers linearring geometry, create a GML linearring.
         *
         * Parameters:
         * geometry - {<OpenLayers.Geometry.LinearRing>} A linearring geometry.
         *
         * Returns:
         * {DOMElement} A GML linearring node.
         */
        linearring: function(geometry) {
            var gml = this.createElementNS(this.gmlns, "gml:LinearRing");
            gml.appendChild(this.buildCoordinatesNode(geometry));
            return gml;
        },
        
        /**
         * Method: buildGeometry.polygon
         * Given an OpenLayers polygon geometry, create a GML polygon.
         *
         * Parameters:
         * geometry - {<OpenLayers.Geometry.Polygon>} A polygon geometry.
         *
         * Returns:
         * {DOMElement} A GML polygon node.
         */
        polygon: function(geometry) {
            var gml = this.createElementNS(this.gmlns, "gml:Polygon");
            var rings = geometry.components;
            var ringMember, ringGeom, type;
            for(var i=0; i<rings.length; ++i) {
                type = (i==0) ? "outerBoundaryIs" : "innerBoundaryIs";
                ringMember = this.createElementNS(this.gmlns,
                                                  "gml:" + type);
                ringGeom = this.buildGeometry.linearring.apply(this,
                                                                   [rings[i]]);
                ringMember.appendChild(ringGeom);
                gml.appendChild(ringMember);
            }
            return gml;
        },
        
        /**
         * Method: buildGeometry.multipolygon
         * Given an OpenLayers multipolygon geometry, create a GML multipolygon.
         *
         * Parameters:
         * geometry - {<OpenLayers.Geometry.MultiPolygon>} A multipolygon
         *     geometry.
         *
         * Returns:
         * {DOMElement} A GML multipolygon node.
         */
        multipolygon: function(geometry) {
            var gml = this.createElementNS(this.gmlns, "gml:MultiPolygon");
            var polys = geometry.components;
            var polyMember, polyGeom;
            for(var i=0; i<polys.length; ++i) {
                polyMember = this.createElementNS(this.gmlns,
                                                  "gml:polygonMember");
                polyGeom = this.buildGeometry.polygon.apply(this,
                                                                [polys[i]]);
                polyMember.appendChild(polyGeom);
                gml.appendChild(polyMember);
            }
            return gml;

        },
 
        /**
         * Method: buildGeometry.bounds
         * Given an OpenLayers bounds, create a GML box.
         *
         * Parameters:
         * bounds - {<OpenLayers.Geometry.Bounds>} A bounds object.
         *
         * Returns:
         * {DOMElement} A GML box node.
         */
        bounds: function(bounds) {
            var gml = this.createElementNS(this.gmlns, "gml:Box");
            gml.appendChild(this.buildCoordinatesNode(bounds));
            return gml;
        }
    },

    /**
     * Method: buildCoordinates
     * builds the coordinates XmlNode
     * (code)
     * <gml:coordinates decimal="." cs="," ts=" ">...</gml:coordinates>
     * (end)
     * Parameters: 
     * geometry - {<OpenLayers.Geometry>} 
     *
     * Returns:
     * {XmlNode} created xmlNode
     */
    buildCoordinatesNode: function(geometry) {
        var coordinatesNode = this.createElementNS(this.gmlns,
                                                   "gml:coordinates");
        coordinatesNode.setAttribute("decimal", ".");
        coordinatesNode.setAttribute("cs", ",");
        coordinatesNode.setAttribute("ts", " ");

        var parts = [];

        if(geometry instanceof OpenLayers.Bounds){
            parts.push(geometry.left + "," + geometry.bottom);
            parts.push(geometry.right + "," + geometry.top);
        } else {
            var points = (geometry.components) ? geometry.components : [geometry];
            for(var i=0; i<points.length; i++) {
                parts.push(points[i].x + "," + points[i].y);                
            }            
        }

        var txtNode = this.createTextNode(parts.join(" "));
        coordinatesNode.appendChild(txtNode);
        
        return coordinatesNode;
    },

    CLASS_NAME: "OpenLayers.Format.RestJSON" 
});
